//
//  HQCapture15ViewController.m
//  精通Objective-C
//
//  Created by 花强 on 2017/12/27.
//  Copyright © 2017年 花强. All rights reserved.
//

#import "HQCapture15ViewController.h"

#import "C15BlockBoject.h"
#import "C15BlockItemObject.h"

#import "C15BlockConcurrentTasks.h"

typedef int (^AdderBlock)(int);

@interface HQCapture15ViewController ()

-(void)testAdderBlock:(AdderBlock)block;

@end

@implementation HQCapture15ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    /*
     
     第15章 块
     
     之前已经介绍了OC平台的所有基础元素，现在介绍一些高级特性及程序中使用它们的方式；
     
     本章介绍在程序中使用块的方式；
        块语法和语义、块内存管理、怎样在程序中开发块和怎样使用现在API（如Foundation）中的块；
     
     块提供了一种方式，使用这种方式可以创建一组语句（即代码块）并将这些语句赋予一个变量，随后就可以调用这个变量；
        从这方面看，块与函数和方法类似，但除了是可执行代码外，块还含有与堆内存和栈内存绑定的变量；
        块就是一个实现的闭包，一个允许访问其常规范围之外变量的函数；
        此外，OC的块实际上就是一个对象，它是NSObject类的子类，拥有NSObject类的所有相关属性（如可以向块发送消息）；
     
     15.1 块的语法
     
     块语法：
        通过支持块的语法功能可以声明块类型的变量和常量；
     
     块类型：
        由返回值类型和参数类型列表构成；
        使用脱字符（^）可以声明块类型的变量；
     形如:
        返回值类型   块的名称          参数类型
        returnType   (^blockName) (parameterType,parameterType,...)
     
     块变量声明语法分析：
        返回值类型指快被调用时返回值的类型；
        快变量名带有一个脱字符前缀并且被封装在括号中；
        参数类型列表被封装在括号中并且位于块名称的后边，参数类型以逗号分隔；
        如无参数，应该将变量的参数类型列表设置为void（封装在括号中）；
        和OC方法声明类似，块变量声明中也可以含有参数名；
     示例：
     */
    int (^testBlock1)(int);
    void (^testBlock2)(int,int);
    void (^testBlock3)(int a,int b);
    int (^testBlock4)(void);
    
    /*
     块类型作为块变量的类型，所以块变量也可以作为函数和方法的参数；
        这种情况下，通常需要通过创建类型定义（typedef）为块类型提供别名，从而起到简化块类型变量名称的作用；
     
     示例：
     typedef int (^AdderBlock)(int);
     @interface HQCapture15ViewController ()
     -(void)testAdderBlock:(AdderBlock)block;
     @end
     
     如上所述，typedef语句用于创建了一个名为AdderBlock的块类型；之后，该块类型被用作方法定义中的参数类型；
     
     块常量表达式：
        使用块常量表达式可以定义对块的引用；
        该表达式以脱字节开头，后跟返回值类型、参数列表和封装在花括号中的语句集合（块的主体）；
     
     形如：
        ^返回值类型          参数
        ^returnType （参数类型 参数名称，...）{
            块代码
            //块语句
        }
     
     语法分析：
        开头的^表示这是一个块常量表达式；
        块参数（传递给函数主体）被封装在括号中，每个块参数都是由一个参数类型和一个参数名称表示，块参数之间以逗号隔开；
        快代码封装在花括号中，由块被调用时执行的语句构成；
     
     示例：
     */
    ^int (int para){
        return para + 1;
    };
    /*
     特殊语法：
     1）没有设置返回值类型的块常量表达式：
        由于编译器可以从块主体中推断出返回值类型，所以也可以不在块定义中为这个块设置返回值类型；
            如下：
            ^(int para){
                return para + 1;
            };
     2）不带参数的块常量定义：
        ^{
            NSLog(@"Hello");
        }
     
     可以将块常量赋予相应的块变量定义：
     示例：
     */
    int (^testBlock5)(int);
    testBlock5 = ^(int para){
        return para + 1;
    };
    //或者直接将块声明和定义组合到一起（要求：参数类型和返回值类型相符合）
    int (^testBlock6)(int) = ^(int para){
        return para + 1;
    };
    
    /*
     上述介绍了块变量和块常量表达式的内容，这里顺便说明下两者的区别，使用是还需注意：
     1）脱字节：
        块变量声明：标示开始，和变量名之前，并一起被封装在圆括号中；
        块常量表达式：标示开始；
     2）名称：
        块变量声明：必选项；
        块常量表达式：无名称；
     3）返回值类型：
        块变量声明：必选项，没有返回值需声明为void；
        块常量表达式：可选项，依靠编译器从块主体推断；块主体的多个return语句返回的数据类型必须相同；
     4）参数：
        块变量声明：必选项，没有需声明为void；
        块常量表达式：可选项；（在无参数列表的条件下）
     
     当声明了块类型变量并将符合其声明的块常量赋予它后，就可以直接使用调用操作符调用该变量；
        还可以使用一条语句定义并调用块表达式；
            因为块表达式是匿名的，因此调用操作符会接收带有符合块表达式的参数列表的匿名函数；
     
     示例：
     */
    int para7 = 9;
    int (^testBlock7)(int) = ^int(int para){
        return para + 1;
    };
    testBlock7(para7);
    
    ^(NSString * user){
        NSLog(@"%@",user);
    }(@"flower");
    /*
     
     还可以像使用匿名函数一样，以内嵌的方式定义块表达式，从而将其用作函数或方法的参数；
        这种情况下，最好仅对函数或方法使用一个块常量参数，并且应将块常量设置为参数列表中的最后一个参数(在有其他参数的情况下)；
 
     块语法我们就介绍完了，这部分内容是立即块用法的基础，需牢记；
 
     15.2 块就是闭包
     
     块就是一个实现的闭包；
        就是一个允许访问在其常规范围外部声明的局部变量的函数（重点理解）；
     
     先来理解一些概念；
     
     变量的可见性/范围：
        指变量在程序的哪个（些）部分中可以被访问；
        C语言的局部变量拥有局部范围；
     
     与C语言的函数相比，块参数通过以下特性提高了变量的可见性：
     1）对词汇范围的支持：
        这是从封闭范围引用局部变量和参数的特性；
     
     2）__block变量：
        __block关键字可以应用于块外部但仍处于统一词汇范围的变量；
        通过该特性可以修改块内部的变量；
     
     3）访问实例变量：
        在对象的方法实现代码中定义的块可以访问该对象中的实例变量；
     
     4）导入参数：
        通过头文件导入的常量变量在块常量表达式中是可见的；
     
     15.2.1 词汇范围
     
     块的一大特性就是支持词汇范围；（C语言函数是无法访问在其定义外部声明的局部变量的）
        块支持词汇范围，因此块常量表达式可以访问在统一词汇范围内声明的变量；
        能够定义变量的地方就可以定义块；（C语言的函数可不是随便定义的）
     
     示例：
     */
    NSString * testStr1 = @"hello";
    C15BlockBoject * bObj1 = [C15BlockBoject new];
    bObj1.blockObj = [C15BlockItemObject new];
    bObj1.blockObj.name = @"haha";
    ^{
        bObj1.blockObj.name = @"hahaaa";
        NSLog(@"%@",testStr1);
    }();
    NSLog(@"%@",bObj1.blockObj.name);
    /*
     log:
     2017-12-27 18:10:14.500474+0800 精通Objective-C[39850:10190403] hello
     2017-12-27 18:10:14.500601+0800 精通Objective-C[39850:10190403] hahaaa
     
     通过词汇范围，块常量表达式可以将局部变量作为常量值（对于原始数据类型）或引用变量（对于对象）访问；
     值得注意的是：
        上述代码如果想修改testStr1的值是办不到的；
        因为，在块常量表达式中通过词汇范围访问的原始数据类型变量是无法修改的；(注意区分上述示例中打印的两个变量的情况)
            这里尝试修改bObj1和testStr1都是不可以的，但是可以修改bObj1的对象属性；
        我们接下来会解决这个问题；
     
     因为局部范围是可以嵌套的，所以块可以捕捉多重（嵌套）范围内的局部变量；
     
     15.2.2 可修改的__block变量
     
     默认情况下，在块常量表达式中通过词汇范围访问的局部变量不能修改；（重点理解）
    
     __block:
        使用存储内容修改符__block可以将这些变量切换为读写模式（使得他们可以被修改）；
        除了C中的变长数组（不是由常数表达式表示长度的数组）和含有变长数组的C语言结构之外，可以对任何OC支持的数据类型使用__block修改符；
        __block不能与局部变量存储修改符auto、register和static组合使用；
     
     当引用变量的块被复制到堆存储区域时，使用__block修改符的变量也会被复制到堆存储区域；
     （不太懂这句话，作为引出块内存管理的话题，我们下一节介绍下该内容）
     
     
     ====
     
     
     15.3 块的内存管理
     
     在运行程序时，块常量表达式会获得栈内存；
        也因此，会拥有与局部变量相同的声明周期；
        如果想在定义范围之外使用块常量表达式的话，就必须复制他们到永久存储区域中（即堆）；
        例如，如果你想要从方法获得类型为块常量的返回值或者存储块常量，就必须将块复制到堆中并在不使用它们时释放这些块；
        否则的话，可能会产生导致程序崩溃的悬挂指针；
     
     示例：在块常量的词汇范围之外使用它
     */
    void (^greetingBlock)(void);
    {//范围开始，将局部变量压入栈（注意这里入栈的是块常量表达式，下边出栈的也是他；而不是greetingBlock这个块变量）
        greetingBlock = ^{
            NSLog(@"Greeting");
        };
    }//范围终点，从栈中弹出变量
    greetingBlock();
    /*
     
     OC为块常量的内存管理提供了复制（Block_copy()）和释放（Block_release()）命令；
        使用Block_copy()命令可以将块常量复制到堆中；
            就像实现了一个将块常量引用作为输入参数并返回相同类型块常量的函数；
        使用Block_release()命令可以释放堆中的块常量；
            就行实现了一个将块常量引用作为输入参数的函数；
            只有当堆中的块引用与块常量对应时，才能从堆中释放块常量引用；否则该函数不会有效果；
        为了避免内存泄漏，Block_copy()和Block_release()命令必须平衡；
     
     Foundation框架提供了处理块的copy和release方法，他们拥有与Block_copy()和Block_release()函数相同的功能；
     
     示例：使用块的复制和释放方法
     void (^greetingBlock1)(void);
     {
         greetingBlock1 = [^{
             NSLog(@"Greeting1");
         } copy];
     }
     greetingBlock1();
     [greetingBlock1 release];
    
        这是在MRC在需要做的；在ARC下，只要块没有返回id类型值或将id类型值用作参数，编译器就会自动执行块的复制和释放操作；
        否则，就必须执行复制和释放操作的方式；
     
     示例：对使用id类型参数的块调用Block_copy()和Block_release()函数
     void (^greetingBlock2)(id solution);
     {
         greetingBlock2 = Block_copy( ^(id solution){
             NSLog(@"Greeting1 %@",solution);
         });
     }
     greetingBlock2(@"flower");
     Block_release( greetingBlock2);
    
     注意：其实这个两个函数也是在MRC下调用的；
     
     __block的不同语义：
        MRR下：
            如果块常量中使用__block变量，那么这些__block变量并不会保留；
        ARC下：
            如果块常量中使用__block变量，那么这些__block变量就会保留；
        这意味着你在使用ARC时不想保留__block变量（如避免循环引用），还应对变量应用__weak存储类型修改符；
     
     我们已经接触了__block和__weak两个存储类型修改符，相应的还有__strong;
     
     15.4 使用块
     
     已经介绍了块语法和一些语义以及内存管理的关键点；
     接下来介绍使用块的方式；
        将块和GCD一同使用（17章将详细介绍GCD），为并行编程提供支持；
        通常，块用于实现小型的、用于封装任务单元的独立代码段；
            他们通常以并行方式（如GCD）执行集合中的多个项目，或者被用作完成操作后的回调函数；
        块还可以使有关联的代码被放在一起，而不会将这些代码分割存储在不同的文件中；
     
     我们来看看块经常使用的情况；
     
     15.4.1 使用块为数组排序
     
     示例：BlockArraySorter
     */
    long ArrayElements = 10;
    //创建一个含有随机数（0~99）的数组
    NSMutableArray * numbers = [NSMutableArray arrayWithCapacity:ArrayElements];
    for (int elem = 0; elem < ArrayElements; elem++) {
        unsigned int value = arc4random() % 100;
        [numbers addObject:[NSNumber numberWithUnsignedInteger:value]];
    }
    NSLog(@"numbers:%@",numbers);
    //以升序方式为数组数值排序
    [numbers sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        if ([obj1 integerValue] > [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedDescending;
        }
        if ([obj1 integerValue] < [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedAscending;
        }
        return (NSComparisonResult)NSOrderedSame;
    }];
    NSLog(@"sorted numbers:%@",numbers);

    /*
     log:
     2017-12-28 14:16:33.395978+0800 精通Objective-C[44631:11171308] numbers:(
     30,
     86,
     79,
     18,
     9,
     56,
     27,
     18,
     15,
     66
     )
     2017-12-28 14:16:33.396128+0800 精通Objective-C[44631:11171308] sorted numbers:(
     9,
     15,
     18,
     18,
     27,
     30,
     56,
     66,
     79,
     86
     )

     分析：
     sortUsingComparator方法接受的是一个NSComparator的块类型；块类型的返回值是NSComparisonResult枚举类型，用于指明请求中项目的顺序；
        NSOrderedAscending
        NSOrderedDescending
        NSOrderedSame
     这个块常量表达式比较了数组元素的值，并返回了以升序排列数组元素的相应NSComparisonResult枚举值；
     
     很多Foundation框架的集合类都含有使用块常量排序其中元素的方法；
     
     接下来再来实践一个使用块实现异步回调函数的程序；
     
     15.4.2 使用块加载URL
     
     示例：BlockURLLoader
     */
    NSString * IndexURL = @"http://www.wikipedia.com/index.html";
    //为连接获取当前的运行循环
    NSRunLoop * loop = [NSRunLoop currentRunLoop];
    BOOL __block downloadComplete = NO;
    //创建请求
    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:IndexURL]];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
                                           if (data == nil) {
                                               NSLog(@"connectionError:%@",[connectionError localizedDescription]);
                                           }else{
                                               NSLog(@"\nDownload %lu bytes from request %@",[data length],[request URL]);
                                           }
                                           downloadComplete = YES;
                                       }];
    
    //一直循环直到完成加载资源的操作为止
    while (!downloadComplete && [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
        //保证程序一直运行直到完成加载资源的操作为止
        //之前也有用过这条语句：总而言之，它会运行循环，接收输入源的事件，并执行所有相应的委托或回调方法，直到连接完成加载资源的操作为止；
    }
    
    /*
     log:
     Download 1242 bytes from request http://www.wikipedia.com/index.html

     很多框架和第三方API都使用了块常量执行回调函数的方法，熟练使用这种方式很有必要；
     
     下面我们编写一个使用块实现并行操作的程序；
     
     15.4.3 使用块的并行编程方式
     
     示例：本应用使用GCD，以并行方式执行通过块定义的过个任务；
     我们新建一个类C15BlockConcurrentTasks来实践这部分内容；
     
     （Code C15BlockConcurrentTasks）
     */
    [[C15BlockConcurrentTasks new] testBlockConcurrentTasks];
    
    /*
     log:
     2017-12-28 17:08:27.412710+0800 精通Objective-C[46079:11385262] =========
     2017-12-28 17:08:28.932882+0800 精通Objective-C[46079:11385382] Time takes 1.519951 seconds,for url http://www.yahoo.com/index.html
     2017-12-28 17:08:30.160462+0800 精通Objective-C[46079:11385374] Time takes 2.747524 seconds,for url http://www.apress.com/index.html
     2017-12-28 17:08:30.160709+0800 精通Objective-C[46079:11385262] Time takes 2.747868 seconds,for all urls

     分析：
        创建了两个全局的并发分派队列（其实就是一个），用以并行方式执行任务；
        创建一个任务组，用于对任务进行编组，为执行异步操作对任务进行排队、等待已排队的任务组等；
        使用dispatch_group_async(group, queue1, getDownloadURLBlock(YahooURL));将两个任务分派给这个任务组；
        被执行的任务是通过C函数获取的块常量设置的；
     
     可以看到，以并行方式执行这些任务所消耗的实践比以异步方式所消耗的时间更少；
     我们将在第17章，详细介绍GCD实现并行操作的编程方式；
     
     15.5 小结
     
     本章介绍了使用块编写程序的方式，块是OC语言提供的一种强大特性；
        具体讨论了块的语法和语义、块的内存管理、使用块编写程序的方式和如何使用现有API中的块；
     
     1）块和函数与方法类似，但块除了是可执行代码，还含有与栈内存和堆内存绑定的变量；
        块就是一个闭包，一个允许访问其常规范围之外变量的函数；
     2）可以将块声明为块类型的变量，在可以使用普通变量的地方就可以使用块对象；
        如作为函数/方法的参数等；
     3）块常量表达式含有调用块时执行的代码；
        可以通过内嵌的方式定义块常量表达式，然后将块用作函数/方法调用的参数；
     4）与C语言相比，通过词汇范围、__block变量、实例变量访问和常数导入，块参数具有更好的可见性；
     5）运行程序时，块常量表达式会获得栈内存，因而拥有与局部变量一样的生命周期；
        因此，需要将他们复制到永久存储区（即堆内存），才能在定义它们的范围之外使用它们；
     6）使用MRR时，使用Block_copy()和Block_release()命令可以复制和释放堆内存中的块；
        使用ARC时，编译器能自动执行复制和释放块的操作（对id上不明确，使用时请避开）；
     7）通常，块用于实现小型的、用于封装任务单元的独立代码段；
        通常以并行的方式执行集合中的多个项目，或者被用作完成操作后的回调函数；
        因为可以通过内嵌方式定义块，所以不用为上下文关联的代码（如异步完成处理程序）创建完全独立的类和方法；
     
     so，你应该仔细学习本章，熟练掌握块的基本使用。
     
     */
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
